package com.insigma.business.qrcode.common.service.impl;


import com.insigma.business.qrcode.common.dao.SequenceDao;
import com.insigma.business.qrcode.common.service.SequenceService;
import com.insigma.business.qrcode.common.util.SequenceException;
import com.insigma.business.qrcode.common.vo.SequenceRange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class SequenceServiceImpl implements SequenceService {
    private static final Logger logger = LoggerFactory.getLogger(SequenceManagerImpl.class);
    private ConcurrentHashMap<String, SequenceRange> globalSequenceRangeMap = new ConcurrentHashMap();
    private Map<String, Semaphore> globalSequenceSemaphoreMap = new ConcurrentHashMap();
    private final Lock lock = new ReentrantLock();
    @Resource
    private TransactionTemplate transactionTemplateNew;
    @Resource
    private SequenceDao sequenceDao;
    private int retryTimes = 1;
    private long minValue;
    private long maxValue;
    private int step = 100;
    private final Random random = new Random();

    public SequenceServiceImpl() {
    }

    public SequenceRange getNextSequenceRange(String sequenceName) {
        String seqNameJoint = sequenceName + "-000";
        Semaphore semaphore = this.getSemaphore(seqNameJoint);
        SequenceRange latestSequenceRange = (SequenceRange)this.globalSequenceRangeMap.get(seqNameJoint);

        try {
            SequenceRange remoteSequenceRange;
            if (null == latestSequenceRange) {
                if (semaphore.tryAcquire(500L, TimeUnit.MILLISECONDS)) {
                    try {
                        latestSequenceRange = (SequenceRange)this.globalSequenceRangeMap.get(seqNameJoint);
                        if (latestSequenceRange == null) {
                            latestSequenceRange = new SequenceRange();
                            latestSequenceRange.setName(sequenceName);
                            latestSequenceRange.setMin(this.getMinValue());
                            latestSequenceRange.setMax(this.getMaxValue());
                            latestSequenceRange.setStep((long)this.getStep());
                            latestSequenceRange.setValue(0L);
                            this.saveOrUpdateSequenceInNewTransaction(latestSequenceRange);
                            this.globalSequenceRangeMap.put(seqNameJoint, latestSequenceRange);
                            remoteSequenceRange = latestSequenceRange;
                            return remoteSequenceRange;
                        }

                        remoteSequenceRange = latestSequenceRange;
                    } finally {
                        semaphore.release();
                    }

                    return remoteSequenceRange;
                }
            } else {
                if (!latestSequenceRange.isOver()) {
                    return latestSequenceRange;
                }

                if (semaphore.tryAcquire(500L, TimeUnit.MILLISECONDS)) {
                    SequenceRange var6;
                    try {
                        latestSequenceRange = (SequenceRange)this.globalSequenceRangeMap.get(seqNameJoint);
                        if (latestSequenceRange != null && !latestSequenceRange.isOver()) {
                            remoteSequenceRange = latestSequenceRange;
                            return remoteSequenceRange;
                        }

                        remoteSequenceRange = this.genNewSequenceInNewTransaction(latestSequenceRange);
                        if (null == remoteSequenceRange) {
                            throw new SequenceException("获取序列号失败：" + sequenceName + "，重试次数：" + this.getRetryTimes());
                        }

                        this.globalSequenceRangeMap.put(seqNameJoint, remoteSequenceRange);
                        var6 = remoteSequenceRange;
                    } finally {
                        semaphore.release();
                    }

                    return var6;
                }
            }
        } catch (Exception var16) {
            logger.error("Sequence " + sequenceName + ", Error : ", var16);
        }

        throw new SequenceException("获取序列号失败：" + sequenceName + "，重试次数：" + this.getRetryTimes());
    }

    public Date getNowDate() {
        return this.sequenceDao.getSystemDate();
    }

    private SequenceRange genNewSequenceInNewTransaction(final SequenceRange localSequenceRange) {
        return (SequenceRange)this.transactionTemplateNew.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus status) {
                try {
                    return SequenceServiceImpl.this.genNewSequence(localSequenceRange);
                } catch (Exception var3) {
                    throw new RuntimeException(var3);
                }
            }
        });
    }

    private SequenceRange genNewSequence(SequenceRange localSequenceRange) {
        try {
            SequenceRange persistSequenceRange = this.sequenceDao.getSequence(localSequenceRange.getTenantCode(), localSequenceRange.getName());
            this.adjustValue((SequenceRange)null, persistSequenceRange);
            int affectedRows = this.sequenceDao.updateSequence(persistSequenceRange);
            if (affectedRows <= 0) {
                logger.error("值超过最大值后重新产生sequence失败，详细：" + localSequenceRange);
                return null;
            } else {
                logger.info("值超过最大值后重新产生sequence成功，详细：" + localSequenceRange);
                return persistSequenceRange;
            }
        } catch (Throwable var4) {
            logger.error("值超过最大值后重新产生sequence失败， " + localSequenceRange, var4);
            return null;
        }
    }

    private long adjustValue(SequenceRange localSequenceRange, SequenceRange persistSequenceRange) {
        long stepValue = persistSequenceRange.getStep();
        long newDbValue;
        if (persistSequenceRange.getValue() == persistSequenceRange.getMax()) {
            newDbValue = persistSequenceRange.getMin();
            if (localSequenceRange != null) {
                localSequenceRange.setValue(persistSequenceRange.getValue());
            }
        } else if (persistSequenceRange.getValue() < persistSequenceRange.getMin()) {
            newDbValue = persistSequenceRange.getMin() + persistSequenceRange.getStep();
            if (localSequenceRange != null) {
                localSequenceRange.setValue(persistSequenceRange.getMin());
            }
        } else {
            newDbValue = persistSequenceRange.getValue() + persistSequenceRange.getStep();
            if (newDbValue >= persistSequenceRange.getMax()) {
                newDbValue = persistSequenceRange.getMin();
            }

            if (localSequenceRange != null) {
                localSequenceRange.setValue(persistSequenceRange.getValue());
            }
        }

        if (newDbValue < persistSequenceRange.getValue()) {
            throw new SequenceException("SEQUENCE-VALUE-ERROR: beginValue" + newDbValue + " is large than value in database " + persistSequenceRange.getValue());
        } else {
            persistSequenceRange.setLatestValue(newDbValue);
            return stepValue;
        }
    }

    private long buildRandomStep(long step) {
        float randomValue = this.random.nextFloat();
        return randomValue > 1.0F ? step : (long)Math.round((float)step * (1.0F + randomValue));
    }

    private void saveOrUpdateSequenceInNewTransaction(final SequenceRange sequenceRange) {
        this.transactionTemplateNew.execute(new TransactionCallbackWithoutResult() {
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    SequenceServiceImpl.this.saveOrUpdateSequence(sequenceRange);
                } catch (Exception var3) {
                    throw new RuntimeException(var3);
                }
            }
        });
    }

    private void saveOrUpdateSequence(SequenceRange sequenceRange) {
        for(int i = 0; i < this.retryTimes; ++i) {
            try {
                SequenceRange queriedSequenceRange = this.sequenceDao.getSequence(sequenceRange.getTenantCode(), sequenceRange.getName());
                if (null == queriedSequenceRange) {
                    sequenceRange.setValue(sequenceRange.getMin() + sequenceRange.getStep());

                    try {
                        this.sequenceDao.insertSequence(sequenceRange);
                        sequenceRange.setValue(sequenceRange.getMin());
                        sequenceRange.setLocalValue(new AtomicLong(sequenceRange.getMin()));
                    } catch (Exception var5) {
                        logger.error("初始化sequence 失败.", var5);
                        throw var5;
                    }
                } else {
                    this.adjustValue(sequenceRange, queriedSequenceRange);
                    int affectedRows = this.sequenceDao.updateSequence(queriedSequenceRange);
                    if (affectedRows <= 0) {
                        logger.warn("更新初始化序列失败：" + queriedSequenceRange.toString());
                        continue;
                    }

                    sequenceRange.setMax(queriedSequenceRange.getMax());
                    sequenceRange.setMin(queriedSequenceRange.getMin());
                    sequenceRange.setStep(queriedSequenceRange.getStep());
                    sequenceRange.setLatestValue(queriedSequenceRange.getLatestValue());
                    logger.info("更新初始化序列成功：" + sequenceRange.toString());
                }

                return;
            } catch (Exception var6) {
                logger.error("新增或修改序列失败：", var6);
            }
        }

        throw new SequenceException("新增或修改序列失败，重试次数：" + this.retryTimes);
    }

    private Semaphore getSemaphore(String seqNameJoint) {
        Semaphore semaphore = (Semaphore)this.globalSequenceSemaphoreMap.get(seqNameJoint);
        if (semaphore != null) {
            return semaphore;
        } else {
            Semaphore var3;
            try {
                this.lock.lock();
                semaphore = (Semaphore)this.globalSequenceSemaphoreMap.get(seqNameJoint);
                if (null == semaphore) {
                    semaphore = new Semaphore(1);
                    this.globalSequenceSemaphoreMap.put(seqNameJoint, semaphore);
                }

                var3 = semaphore;
            } finally {
                this.lock.unlock();
            }

            return var3;
        }
    }

    public long getMinValue() {
        return this.minValue;
    }

    public void setMinValue(long minValue) {
        this.minValue = minValue;
    }

    public long getMaxValue() {
        return this.maxValue;
    }

    public void setMaxValue(long maxValue) {
        this.maxValue = maxValue;
    }

    public int getStep() {
        return this.step;
    }

    public void setStep(int step) {
        this.step = step;
    }

    public int getRetryTimes() {
        return this.retryTimes;
    }

    public void setRetryTimes(int retryTimes) {
        this.retryTimes = retryTimes;
    }

    public void setTransactionTemplateNew(TransactionTemplate transactionTemplateNew) {
        this.transactionTemplateNew = transactionTemplateNew;
    }

    public void setSequenceDao(SequenceDao sequenceDao) {
        this.sequenceDao = sequenceDao;
    }
}
